Skip to content

nonblocking check of pollables when reactor is busy #78

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

pchickey
Copy link
Contributor

@pchickey pchickey commented Aug 8, 2025

Fixes #73.

Add test provided by @SilverMira in #70, tracked in #73.

Add a nonblock_check_pollables function to the reactor, for use in the "busy case" of the block_on loop. Factor out check_pollables, the common part of block_on_pollables and nonblock_check_pollables.

@pchickey pchickey assigned yoshuawuyts and unassigned yoshuawuyts Aug 8, 2025
@pchickey pchickey requested a review from yoshuawuyts August 8, 2025 20:21
Comment on lines +386 to +399
let mut future_group = futures_concurrency::future::FutureGroup::<
Pin<Box<dyn std::future::Future<Output = bool>>>,
>::new();
future_group.insert(Box::pin(cpu_heavy));
future_group.insert(Box::pin(timeout));
let result = futures_lite::StreamExt::next(&mut future_group).await;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised this doesn't work here? Afaict this is just race-semantics?

Suggested change
let mut future_group = futures_concurrency::future::FutureGroup::<
Pin<Box<dyn std::future::Future<Output = bool>>>,
>::new();
future_group.insert(Box::pin(cpu_heavy));
future_group.insert(Box::pin(timeout));
let result = futures_lite::StreamExt::next(&mut future_group).await;
let result = (cpu_heavy, timeout).race().await;

Assuming futures_concurrency::prelude::*; is imported of course.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just tried this change, and it doesn't actually trigger the broken behavior that the existing test case does. I don't actually understand why, though.

Comment on lines +147 to +161
static READY_POLLABLE: LazyLock<Pollable> =
LazyLock::new(|| wasi::clocks::monotonic_clock::subscribe_duration(0));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha, it feels like we're almost missing a built-in for this in the component model?

cc/ @lukewagner check this out

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that there is a non-blocking mechanism to check if a waitable set contains any ready waitables in p3, so this wont be an issue there.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, that's right @pchickey: calling waitable-set.poll or returning the POLL code from a callback is non-blocking.

Comment on lines 177 to 181
// If no wakers are pending on pollables, there is no work to be done
// here:
if reactor.wakers.is_empty() {
return;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one caught me off guard a little. I assume it's correct, but I'm not sure why it's correct. Can you elaborate a little more in the comment?

On line 135 we panic if the list of pollables is empty, but apparently having an empty list of wakers is not a problem. Saying a little more about that would be helpful to me at least.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good catch, this would have subtly introduced bad behavior in the blocking case, so I added a commit that changes how this is implemented. In the nonblocking case (i.e. the root future is pending but has been woken), we want to skip these calculations and poll call if it cannot wake any other futures. In the blocking case, we want to panic with the most informative message possible, rather than rely on a debug assert, or let wasi trap and the user have to work out why.

pchickey and others added 3 commits August 11, 2025 11:52
as provided by @SilverMira in #70, and tracked in #73

Co-authored-by: SilverMira <[email protected]>
sharing most of the implementation with block_on_pollables
in the nonblocking case, we can skip work. in the blocking case, we need
to panic. In the absence of a panic, either the debug assert in
block_on_pollables will go off, or the wasi poll() will trap.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Futures should be woken before calling Future::poll
3 participants